﻿using System;
using System.Collections.Generic;
using System.Diagnostics;

using Atomic.Libraries;

namespace Atomic.Structures
{
	/// <summary>
	/// A placeholder for one of multiple possible atoms at one site. Used with <see cref="StructureGenerator" />.
	/// </summary>
	[Serializable]
	[DebuggerDisplay("{Symbol}")]
	public class PlaceholderAtom : Atom
	{
		private int hashCode;

		internal PlaceholderAtom(IEnumerable<Atom> atoms)
		{
			List<Atom> filteredAtoms = new List<Atom>();
			hashCode = 0;
			foreach (Atom atom in atoms)
			{
				Add(filteredAtoms, ref hashCode, atom);
			}
		
			if (filteredAtoms.Count <= 1)
			{
				throw new ArgumentException("At least distinct two atoms needed for a placeholder.");
			}

			Atoms = new ReadOnlyList<Atom>(filteredAtoms);
		}

		private static void Add(List<Atom> filteredAtoms, ref int hashCode, Atom atom)
		{
			if (atom is PlaceholderAtom)
			{
				// Flatten out nested placeholders.
				foreach (Atom atom2 in ((PlaceholderAtom)atom).Atoms)
				{
					Add(filteredAtoms, ref hashCode, atom2);
				}
			}
			else if (!filteredAtoms.Contains(atom))
			{
				filteredAtoms.Add(atom);

				// Compute a symmetric hash code of the added atoms (i.e. interchanging
				// two atoms doesn't change the hash code). Xor has this property.
				hashCode ^= atom.GetHashCode();
			}
		}

		public override Atom SpinUnpolarize()
		{
			return new PlaceholderAtom(new List<Atom>(Atoms).ConvertAll<Atom>(a => a.SpinUnpolarize()));
		}

		/// <summary>
		/// Creates a new placeholder with the specified atom removed. If only one atom is left after the removal, that atom is returned instead.
		/// </summary>
		public Atom Remove(Atom atom)
		{
			List<Atom> atoms = new List<Atom>(Atoms).FindAll(a => a != atom);
			if (atoms.Count == 1)
			{
				return atoms[0];
			}
			else
			{
				return new PlaceholderAtom(atoms);
			}
		}

		public override bool Equals(Atom other)
		{
			return Equals(other as PlaceholderAtom);
		}

		public bool Equals(PlaceholderAtom other)
		{
			if (object.ReferenceEquals(other, null))
			{
				return false;
			}

			if (object.ReferenceEquals(this, other))
			{
				return true;
			}

			if (GetType() != other.GetType())
			{
				return false;
			}

			if (GetHashCode() != other.GetHashCode())
			{
				return false;
			}

			if (Atoms.Count != other.Atoms.Count)
			{
				return false;
			}

			foreach (Atom atom in Atoms)
			{
				if (!other.Atoms.Contains(atom))
				{
					return false;
				}
			}

			return true;
		}

		public override int GetHashCode()
		{
			return hashCode;
		}

		public static PlaceholderAtom ParsePlaceholder(string symbol)
		{
			return new PlaceholderAtom(new List<string>(symbol.Split(',')).ConvertAll(s => Atom.Parse(s.Trim(' '), false)));
		}

		public ReadOnlyList<Atom> Atoms
		{
			get;
			private set;
		}

		public override string Symbol
		{
			get
			{
				return string.Join(",", new List<Atom>(Atoms).ConvertAll<string>(atom => atom.Symbol));
			}
		}

		public override int Number
		{
			get
			{
				// Not defined for this pseudo-atom.
				throw new NotSupportedException();
			}
		}

		public override double Mass
		{
			get
			{
				// Not defined for this pseudo-atom.
				throw new NotSupportedException();
			}
		}

		public override string ElectronConfiguration
		{
			get
			{
				return null;
			}
		}
	}
}
